using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace CSW.Framework.Common
{
    /// <summary>
    /// Read only BindingList.  Allows the list to be sealed from adding, removing or replacing items.
    /// </summary>
    /// <typeparam name="T">The type.</typeparam>
    public class ReadOnlyBindingList<T> : BindingList<T>
    {
        private bool m_IsReadOnly;

        /// <summary>
        /// Initializes a new instance of the <see cref="ReadOnlyBindingList&lt;T&gt;"/> class.
        /// </summary>
        public ReadOnlyBindingList()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ReadOnlyBindingList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="list">An <see cref="T:System.Collections.Generic.IList`1"/> of items to be contained in the <see cref="T:System.ComponentModel.BindingList`1"/>.</param>
        public ReadOnlyBindingList(IList<T> list)
            : base(list)
        {
        }

        /// <summary>
        /// ListModifier, used to modify a <see cref="ReadOnlyBindingList&lt;T&gt;"/>. See <see cref="ReadOnlyBindingList&lt;T&gt;.MakeReadOnly"/>.
        /// </summary>
        public class ListModifier
        {
            private readonly ReadOnlyBindingList<T> m_List;

            internal ListModifier(ReadOnlyBindingList<T> list)
            {
                Guard.ArgumentNotNull(list, "list");
                m_List = list;
            }

            /// <summary>
            /// Adds a new item to the end of the collection.
            /// </summary>
            /// <returns>
            /// The item that was added to the collection.
            /// </returns>
            /// <exception cref="T:System.InvalidCastException">The new item is not the same type as the objects contained in the <see cref="T:System.ComponentModel.BindingList`1"></see>.</exception>
            public object AddNewCore() { return m_List.PrivateAddNewCore(); }
            /// <summary>
            /// Removes all elements from the collection.
            /// </summary>
            public void ClearItems() { m_List.PrivateClearItems(); }
            /// <summary>
            /// Inserts the specified item in the list at the specified index.
            /// </summary>
            /// <param name="index">The zero-based index where the item is to be inserted.</param>
            /// <param name="item">The item to insert in the list.</param>
            public void InsertItem(int index, T item) { m_List.PrivateInsertItem(index, item); }
            /// <summary>
            /// Removes the item at the specified index.
            /// </summary>
            /// <param name="index">The zero-based index of the item to remove.</param>
            public void RemoveItem(int index) { m_List.PrivateRemoveItem(index); }
            /// <summary>
            /// Replaces the item at the specified index with the specified item.
            /// </summary>
            /// <param name="index">The zero-based index of the item to replace.</param>
            /// <param name="item">The new value for the item at the specified index. The value can be null for reference types.</param>
            /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
            public void SetItem(int index, T item) { m_List.PrivateSetItem(index, item); }
        }

        private object PrivateAddNewCore() { return base.AddNewCore(); }
        private void PrivateClearItems() { base.ClearItems(); }
        private void PrivateInsertItem(int index, T item) { base.InsertItem(index, item); }
        private void PrivateRemoveItem(int index) { base.RemoveItem(index); }
        private void PrivateSetItem(int index, T item) { base.SetItem(index, item); }

        /// <summary>
        /// Seals the list from adding or removing items.
        /// </summary>
        public ListModifier MakeReadOnly()
        {
            CheckEdittable();

            m_IsReadOnly = true;

            return new ListModifier(this);
        }

        /// <summary>
        /// Gets a value indicating whether the list is sealed from adding, removing or replacing items.
        /// </summary>
        /// <value><c>true</c> if the list is sealed from adding, removing or replacing items; otherwise, <c>false</c>.</value>
        public bool IsReadOnly
        {
            get { return m_IsReadOnly; }
        }

        private void CheckEdittable()
        {
            if (m_IsReadOnly)
                throw new Exception("The list is read only.");
        }

        /// <summary>
        /// Adds a new item to the end of the collection.
        /// </summary>
        /// <returns>
        /// The item that was added to the collection.
        /// </returns>
        /// <exception cref="T:System.InvalidCastException">The new item is not the same type as the objects contained in the <see cref="T:System.ComponentModel.BindingList`1"></see>.</exception>
        protected override object AddNewCore()
        {
            CheckEdittable();
            return base.AddNewCore();
        }

        /// <summary>
        /// Removes all elements from the collection.
        /// </summary>
        protected override void ClearItems()
        {
            CheckEdittable();
            base.ClearItems();
        }

        /// <summary>
        /// Inserts the specified item in the list at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index where the item is to be inserted.</param>
        /// <param name="item">The item to insert in the list.</param>
        protected override void InsertItem(int index, T item)
        {
            CheckEdittable();
            base.InsertItem(index, item);
        }

        /// <summary>
        /// Removes the item at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the item to remove.</param>
        protected override void RemoveItem(int index)
        {
            CheckEdittable();
            base.RemoveItem(index);
        }

        /// <summary>
        /// Replaces the item at the specified index with the specified item.
        /// </summary>
        /// <param name="index">The zero-based index of the item to replace.</param>
        /// <param name="item">The new value for the item at the specified index. The value can be null for reference types.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero.-or-index is greater than <see cref="P:System.Collections.ObjectModel.Collection`1.Count"></see>.</exception>
        protected override void SetItem(int index, T item)
        {
            CheckEdittable();
            base.SetItem(index, item);
        }
    }
}